package com.wei.czz.common.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wei.czz.common.annotation.ChargeRequest;
import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.constant.EntityConstant;
import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.enums.external.ExternalBusinessTypeEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.tool.Result;
import com.wei.czz.common.utils.ContextUtils;
import com.wei.czz.common.utils.RsaUtils;
import com.wei.czz.common.utils.TimeUtils;
import com.wei.czz.framework.common.handler.redis.RedisHashHandler;
import com.wei.czz.framework.common.handler.redis.RedisHandler;
import com.wei.czz.framework.common.handler.redis.RedisSetHandler;
import com.wei.czz.common.vo.external.request.ExternalRequestVo;
import com.wei.czz.framework.common.service.AesService;
import com.wei.czz.framework.common.service.AsyncService;
import com.wei.czz.framework.external.entity.ExternalBusinessEntity;
import com.wei.czz.framework.external.entity.ExternalRequestEntity;
import com.wei.czz.framework.external.entity.ExternalUserEntity;
import com.wei.czz.framework.external.service.ExternalBusinessService;
import com.wei.czz.framework.external.service.ExternalRequestService;
import com.wei.czz.framework.external.service.ExternalUserService;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2024-05-23 17:45:26
 * className: ChargeRequestAspect 收费请求切片类
 * version: 1.0
 * description:
 */
@Order(4)
@Aspect
@Component
@AllArgsConstructor
public class ChargeRequestAspect {

    private static final Logger log = LoggerFactory.getLogger(ChargeRequestAspect.class);

    private static final ObjectMapper objectMapper = new ObjectMapper();

    private final ExternalUserService externalUserService;

    private final ExternalBusinessService externalBusinessService;

    private final ExternalRequestService externalRequestService;

    private final AesService aesService;

    private final AsyncService asyncService;

    private final RedisHandler redisHandler;

    private final RedisSetHandler redisSetHandler;

    private final RedisHashHandler redisHashHandler;

    /**
     * 所有使用 ChargeRequest 注解的方法作为切点
     */
    @Pointcut("@annotation(com.wei.czz.common.annotation.ChargeRequest)")
    public void annotationPoint() {}

    /**
     * com.wei.blog.framework包路径下，index模块、common模块和system模块的controller包下，所有的接口方法作为切点
     */
    @Pointcut("execution(* com.wei.czz.framework.*.controller.*.*(..))")
    public void controllerPoint() {}


    @Around("annotationPoint() && controllerPoint()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        // 获取请求源的ip地址
        String requestIp = ContextUtils.get(Constant.REQUEST_IP);
        // 获取注解对象
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        ChargeRequest chargeRequest = methodSignature.getMethod().getAnnotation(ChargeRequest.class);
        if (Objects.isNull(chargeRequest)) {
            log.error("获取ChargeRequest对象为空");
            return Result.fail();
        }

        ExternalRequestVo externalRequestVo = this.getExternalRequestVo(pjp.getArgs());
        log.info("外部请求参数：{}", externalRequestVo);
        if (Objects.isNull(externalRequestVo)) {
            return Result.clientFail("缺少请求参数，请求被拒绝");
        }

        /*
            获取请求用户
         */
        ExternalUserEntity externalUser = externalUserService.find(externalRequestVo.getUserCode());
        if (Objects.isNull(externalUser)) {
            log.info("请求用户不存在。userCode={}", externalRequestVo.getUserCode());
            return Result.clientFail("您不是系统用户，请求被拒绝");
        }
        if (CommonEnum.ONE.getValue().equals(externalUser.getStatus())) {
            log.info("用户已停用。id={} status={}", externalUser.getId(), externalUser.getStatus());
            return Result.clientFail("你的用户已被系统停用，请确认后重试");
        }
        if (CommonEnum.ZERO.getValue().equals(externalUser.getIpRestrictStatus())) {
            // 限制请求ip
            String cacheKey = RedisConstant.SET + EntityConstant.EXTERNAL_IP + Constant.SPLIT
                    + externalRequestVo.getUserCode();
            Boolean bool = redisSetHandler.isMember(cacheKey, requestIp);
            if (Boolean.FALSE.equals(bool)) {
                log.info("请求ip不是用户备案ip。id={} requestIp={}", externalUser.getId(), requestIp);
                return Result.clientFail("您的请求发起机器ip没有在系统中备案，请确认后重试");
            }
        }
        // 外部用户私钥解密
        String privateKey = aesService.czzDecrypt(externalUser.getPrivateKey());

        /*
            参数解密
         */
        String userCode;
        String businessType;
        String secretKey;
        String iv;
        try {
            String requestKey = RsaUtils.decodeRsa(externalRequestVo.getRequestKey(), privateKey);
            log.info("请求键解密结果：{}", requestKey);
            String[] array = requestKey.split(Constant.SPLIT);
            userCode = array[0];
            businessType = array[1];
            secretKey = array[2];
            iv = array[3];
        } catch (Exception e) {
            log.error("参数携带的请求键解析异常。message={}", e.getMessage(), e);
            return Result.clientFail("请求键解密失败，请确认后重试");
        }

        if (!externalUser.getUserCode().equals(userCode)) {
            log.info("参数携带用户编号和数据库存储用户编号不一致，请求被拒绝。param.userCode={} requestKey.userCode={} db.userCode={}",
                    externalRequestVo.getUserCode(), userCode, externalUser.getUserCode());
            return Result.clientFail("用户编号非法，请确认后重试");
        }

        if (StringUtils.isBlank(businessType)) {
            log.error("请求键没有携带业务类型");
            return Result.clientFail("请求键没有携带业务类型，请求被拒绝");
        }
        int business;
        try {
            business = Integer.parseInt(businessType);
        } catch (NumberFormatException e) {
            log.info("业务类型不是数值。businessType={}", businessType);
            return Result.clientFail("业务类型不是数值，请求被拒绝");
        }
        ExternalBusinessTypeEnum externalBusinessTypeEnum = chargeRequest.value();
        if (!externalBusinessTypeEnum.getValue().equals(business)) {
            log.info("业务类型不是注解标记的业务类型。annotation.businessType={} businessType={}",
                    externalBusinessTypeEnum.getValue(), businessType);
            return Result.clientFail("请求业务类型错误，请确认后重试");
        }
        String businessTypeName = externalBusinessTypeEnum.getName();

        /*
            获取用户开通业务对象
         */
        ExternalBusinessEntity externalBusiness = externalBusinessService.get(userCode, business);
        if (Objects.isNull(externalBusiness)) {
            log.info("用户没有开通业务。userCode={} businessType={}", userCode, businessType);
            String message = String.format("您没有开通‘%s’业务，请确认后重试", businessTypeName);
            return Result.clientFail(message);
        }
        Date date = new Date();
        if (date.compareTo(externalBusiness.getEffectStartTime()) < 0) {
            log.info("用户开通业务还没到生效时间。id={} effectStartTime={}", externalBusiness.getId(),
                    TimeUtils.format(externalBusiness.getEffectStartTime()));
            String message = String.format("您开通的‘%s’业务还没到生效时间，请确认后重试", businessTypeName);
            return Result.clientFail(message);
        }
        if (date.compareTo(externalBusiness.getEffectEndTime()) > 0) {
            log.info("用户开通业务已过期。id={} effectEndTime={}", externalBusiness.getId(),
                    TimeUtils.format(externalBusiness.getEffectEndTime()));
            String message = String.format("您开通的‘%s’业务已过期，请确认后重试", businessTypeName);
            return Result.clientFail(message);
        }
        if (CommonEnum.ONE.getValue().equals(externalBusiness.getStatus())) {
            log.info("用户开通业务已停用。id={} status={}", externalBusiness.getId(), externalBusiness.getStatus());
            String message = String.format("您开通的‘%s’业务已停用，请确认后重试", businessTypeName);
            return Result.clientFail(message);
        }

        if (CommonEnum.ZERO.getValue().equals(externalBusiness.getChargeMode())) {
            /*
                请求计数
            */
            // 获取用户开通业务请求次数扣减lua脚本
            String redisLua = this.luaScript();
            String cacheKey = RedisConstant.HASH + EntityConstant.EXTERNAL_BUSINESS + Constant.SPLIT + userCode;
            Long count = redisHandler.executeLua(redisLua, Arrays.asList(cacheKey, businessType), Collections.emptyList(),
                    Long.class);
            log.info("用户开通业务请求次数扣除完成。id={} businessType={} count={}", externalBusiness.getId(),
                    businessTypeName, count);
            if (Long.valueOf(-2).equals(count)) {
                log.error("用户开通业务缓存未配置。id={} businessType={}", externalBusiness.getId(), businessType);
                return Result.fail();
            }
            if (Long.valueOf(-1).equals(count)) {
                String message = String.format("您开通的‘%s’业务已用完请求次数，请确认后重试", businessTypeName);
                return Result.clientFail(message);
            }
        }

        ExternalRequestEntity externalRequest = new ExternalRequestEntity();
        externalRequest.setUserCode(userCode)
                .setBusinessType(business)
                .setRequestData(StringUtils.EMPTY)
                .setRequestIp(requestIp)
                .setRequestTime(date)
                .setRequestTrance(MDC.get(Constant.TRACE_ID))
                .setResponseData(StringUtils.EMPTY)
                .setDeleteStatus(CommonEnum.ZERO.getValue())
                .setUpdateTime(date)
                .setUpdateUser(StringUtils.EMPTY)
                .setUpdateUserId(CommonEnum.ZERO.getLongValue())
                .setCreateTime(date)
                .setCreateUser(externalUser.getNickName())
                .setCreateUserId(externalUser.getId());

        /*
            请求参数解密
         */
        String plainText = aesService.decrypt(externalRequestVo.getData(), secretKey, iv, false);
        if (StringUtils.isBlank(plainText)) {
            log.info("请求参数解密失败。");
            return Result.clientFail("请求参数解密失败，请确认后重试");
        }
        // 解密结果放回参数对象中
        externalRequestVo.setData(plainText);
        // 记录请求参数
        externalRequest.setRequestData(plainText);

        try {
            // 执行目标方法
            Object result = pjp.proceed();
            if (result instanceof Result) {
                Result<?> r = (Result<?>) result;
                if (r.isSuccess()) {
                    String responseData = this.toJson(r.getData());
                    // 请求结果加密
                    String cipherText = aesService.encrypt(responseData, secretKey, iv, false);
                    result = Result.success(cipherText);
                    // 记录响应结果
                    externalRequest.setResponseData(responseData);
                }
                externalRequest.setResponseCode(r.getCode())
                        .setResponseMsg(r.getMsg());

            } else {
                externalRequest.setResponseCode(ResultEnum.SUCCESS.getCode())
                        .setResponseMsg(ResultEnum.SUCCESS.getMsg());
            }
            return result;
        } catch (Throwable throwable) {
            if (throwable instanceof CzzException) {
                CzzException e = (CzzException) throwable;
                externalRequest.setResponseCode(e.getCode())
                        .setResponseMsg(e.getMessage());
            } else {
                externalRequest.setResponseCode(ResultEnum.SERVICE_FAIL.getCode())
                        .setResponseMsg(throwable.getMessage());
            }
            // 此处继续往外抛出异常，让全局异常配置可以监听到
            throw throwable;
        } finally {
            // 设置请求响应时间
            externalRequest.setResponseTime(new Date());
            log.info("请求收费：userCode={} businessType={} 耗时：{}毫秒", userCode, businessType,
                    externalRequest.getResponseTime().getTime() - externalRequest.getRequestTime().getTime());
            asyncService.execute(() -> {
                /*
                    保存请求记录
                 */
                try {
                    // 请求参数和响应结果加密存储
                    if (StringUtils.isNotBlank(externalRequest.getRequestData())) {
                        externalRequest.setRequestData(aesService.czzEncrypt(externalRequest.getRequestData()));
                    }
                    if (StringUtils.isNotBlank(externalRequest.getResponseData())) {
                        externalRequest.setResponseData(aesService.czzEncrypt(externalRequest.getResponseData()));
                    }
                    externalRequestService.saveOrEdit(externalRequest);
                } catch (Exception e) {
                    log.error("保存用户请求记录异常。externalRequest={} message={}", externalRequest, e.getMessage(), e);
                }

                boolean compensate = ResultEnum.getServiceFailList().stream()
                        .anyMatch(resultEnum -> resultEnum.getCode().equals(externalRequest.getResponseCode()));
                if (compensate) {
                    /*
                        用户请求次数补偿
                     */
                    String cacheKey = RedisConstant.HASH + EntityConstant.EXTERNAL_BUSINESS + Constant.SPLIT
                            + userCode;
                    try {
                        Long count = redisHashHandler.increment(cacheKey, businessType, 1);
                        log.info("用户请求次数补偿完成。id={} businessType={} count={}", externalBusiness.getId(),
                                businessTypeName, count);
                    } catch (Exception e) {
                        log.error("用户请求次数补偿操作异常。cacheKey={} message={}", cacheKey, e.getMessage(), e);
                    }
                } else {
                    /*
                        用户请求使用次数增加扣减
                     */
                    externalBusinessService.incUseRequestCount(externalBusiness.getId());
                }
            });
        }
    }

    /**
     * 获取请求参数
     * @param args 请求参数对象数组
     * @return 请求参数
     */
    private ExternalRequestVo getExternalRequestVo(Object[] args) {
        for (Object object : args) {
            if (object instanceof  ExternalRequestVo) {
                return (ExternalRequestVo) object;
            }
        }
        return null;
    }

    /**
     * 获取扣减用户开通业务请求次数lua脚本
     * @return lua脚本
     */
    private String luaScript() {
        String luaString =
                "local res = redis.call('HGET',KEYS[1],KEYS[2]) " +
                "if (res == false) then" +
                "  return -2 " +
                "elseif (tonumber(res) > 0) then" +
                "  return redis.call('HINCRBY',KEYS[1],KEYS[2],-1) " +
                "else" +
                "  return -1 " +
                "end ";
        log.debug("luaScript={}", luaString);
        return luaString;
    }

    /**
     * 对象序列化
     * @param object 参数对象
     * @return json字符串
     */
    private String toJson(Object object) {
        try {
            return objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            log.info("请求收费计算，对象序列化异常。message={}", e.getMessage(), e);
            return StringUtils.EMPTY;
        }
    }

}
